﻿using System;
using System.Collections.Generic;
using System.DirectoryServices.AccountManagement;
using System.Security.Principal;
using System.Linq;

namespace SharePoint.UserAccountControl
{
    /// <summary>
    /// A utility class that contains security functions.
    /// </summary>
    public static class Security
    {
        /// <summary>
        /// Removes the name of the domain from user.
        /// </summary>
        /// <param name="userName">Name of the user.</param>
        /// <returns>The user name without the domain prefix.</returns>
        public static string RemoveDomainFromUserName(string userName)
        {
            var pos = userName.LastIndexOf('\\');
            if (pos != -1)
            {
                userName = userName.Substring(pos + 1, userName.Length - pos - 1);
            }

            return userName;
        }

        /// <summary>
        /// Gets the SID of the specified user as a byte array.
        /// </summary>
        /// <param name="securityIdentifier">The security identifier.</param>
        /// <returns>A byte array representing the security identifier.</returns>
        public static byte[] GetSid(SecurityIdentifier securityIdentifier)
        {
            var sid = new byte[securityIdentifier.BinaryLength];
            securityIdentifier.GetBinaryForm(sid, 0);
            return sid;
        }

        /// <summary>
        /// Returns true if the specified sid is in the list of sids.
        /// </summary>
        /// <param name="sids">The sids.</param>
        /// <param name="sidString">The sid.</param>
        /// <returns>True if the sid is in the list.</returns>
        public static bool CompareSid(IEnumerable<SecurityIdentifier> sids, string sidString)
        {
            var sid = new SecurityIdentifier(sidString);
            return CompareSid(sids, sid);
        }

        /// <summary>
        /// Returns true if the specified sid is in the list of sids.
        /// </summary>
        /// <param name="sidsA">The sids.</param>
        /// <param name="sidsB">The sids.</param>
        /// <returns>True if any of the sids in the the lists.</returns>
        public static bool CompareSid(IEnumerable<SecurityIdentifier> sidsA, IEnumerable<SecurityIdentifier> sidsB)
        {
            foreach(var sidA in sidsB)
            {
                if(CompareSid(sidsA, sidA))
                {
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Returns true if the specified sid is in the list of sids.
        /// </summary>
        /// <param name="sids">The sids.</param>
        /// <param name="sid">The sid.</param>
        /// <returns>True if the sid is in the list.</returns>
        public static bool CompareSid(IEnumerable<SecurityIdentifier> sids, SecurityIdentifier sid)
        {
            return sids.Where(s => CompareSid(sid, s)).Count() > 0;
        }

        /// <summary>
        /// Compares the sids.
        /// </summary>
        /// <param name="sidA">The first sid.</param>
        /// <param name="sidStringB">The second sid.</param>
        /// <returns>True if the two sids are equivalent.</returns>
        public static bool CompareSid(SecurityIdentifier sidA, string sidStringB)
        {
            var sidB = new SecurityIdentifier(sidStringB);
            return CompareSid(sidA, sidB);
        }

        /// <summary>
        /// Compares the sids.
        /// </summary>
        /// <param name="sidStringA">The first sid.</param>
        /// <param name="sidStringB">The second sid.</param>
        /// <returns>True if the two sids are equivalent.</returns>
        public static bool CompareSid(string sidStringA, string sidStringB)
        {
            var sidA = new SecurityIdentifier(sidStringA);
            var sidB = new SecurityIdentifier(sidStringB);
            return CompareSid(sidA, sidB);
        }

        /// <summary>
        /// Compares the sids.
        /// </summary>
        /// <param name="sidA">The first sid.</param>
        /// <param name="sidB">The second sid.</param>
        /// <returns>True if the two sids are equivalent.</returns>
        public static bool CompareSid(SecurityIdentifier sidA, SecurityIdentifier sidB)
        {
            return sidA.Equals(sidB);
        }

        /// <summary>
        /// Gets the user.
        /// </summary>
        /// <param name="userName">The user name.</param>
        /// <param name="contextType">The context.</param>
        /// <returns>The <see cref="UserInfo"/> or null if not found.</returns>
        private static UserInfo GetUser(string userName, ContextType contextType)
        {
            var context = new PrincipalContext(contextType);
            var userPrincipal = UserPrincipal.FindByIdentity(context, IdentityType.SamAccountName, userName);

            // Check is user
            if (userPrincipal != null)
            {
                // Get all groups the user is a member of (including nested groups)
                var userGroups = userPrincipal.GetAuthorizationGroups();

                return new UserInfo
                {
                    UserName = userName,
                    Sid = userPrincipal.Sid,
                    GroupSids = userGroups.Select(g => g.Sid).ToList(),
                    IsGroup = false
                };
            }
            return null;
        }

        /// <summary>
        /// Gets the group.
        /// </summary>
        /// <param name="userName">The user name.</param>
        /// <param name="contextType">The context.</param>
        /// <returns>The <see cref="UserInfo"/> or null if not found.</returns>
        private static UserInfo GetGroup(string userName, ContextType contextType)
        {
            var context = new PrincipalContext(contextType);

            var groupPrincipal = GroupPrincipal.FindByIdentity(context, IdentityType.SamAccountName, userName);
            if (groupPrincipal != null)
            {
                // Get all groups the user is a member of (including nested groups)
                var groupGroups = groupPrincipal.GetGroups();

                return new UserInfo
                {
                    UserName = groupPrincipal.Name,
                    Sid = groupPrincipal.Sid,
                    GroupSids = groupGroups.Select(g => g.Sid).ToList(),
                    IsGroup = true
                };
            }
            return null;
        }

        /// <summary>
        /// Gets info about the user or group.
        /// </summary>
        /// <param name="userName">The user or group name.</param>
        /// <returns>Info about the user.</returns>
        public static UserInfo GetUserInfo(string userName)
        {
            // Try domain user
            var userInfo = GetUser(userName, ContextType.Domain);
            if (userInfo != null)
            {
                return userInfo;
            }

            // Try domain group
            userInfo = GetGroup(userName, ContextType.Domain);
            if (userInfo != null)
            {
                return userInfo;
            }

            // Try machine user
            userInfo = GetUser(userName, ContextType.Machine);
            if (userInfo != null)
            {
                return userInfo;
            }

            // Try machine group
            userInfo = GetGroup(userName, ContextType.Machine);
            if (userInfo != null)
            {
                return userInfo;
            }

            // Not found
            throw new NoMatchingPrincipalException(string.Format("Could not find user or group '{0}'.", userName));
        }

    }
}
